{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Solver Parameters Tutorial\n",
    "For instructions on how to run these tutorial notebooks, please see the [index](./index.ipynb).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setting solver parameters\n",
    "Many solvers allow the users to adjust the parameters. When calling `Solve()` function, Drake will use the default parameters for the solver (iterations, optimality tolerance, etc). You could modify these parameters in two ways, by either calling `MathematicalProgram::SetSolverOption`, or pass a `SolverOptions` argument to the `Solve()` function."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Calling MathematicalProgram::SetSolverOption\n",
    "By calling `MathematicalProgram::SetSolverOption(solver_id, option_name, option_value)`, you can set a parameter for a specific solver (with the matching `solver_id`). The `option_name` is specific to that solver (for example, [here](https://coin-or.github.io/Ipopt/OPTIONS.html) is a list of IPOPT parameters). Note that `MathematicalProgram` object will store this solver parameter, and this parameter will be applied in the `Solve()` call, if that specific solver (with the matching `solver_id`) is invoked.\n",
    "\n",
    "In the following code snippet, we show an example of setting the options of IPOPT."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pydrake.solvers import MathematicalProgram, SolverOptions, Solve\n",
    "from pydrake.solvers import IpoptSolver\n",
    "import numpy as np\n",
    "prog = MathematicalProgram()\n",
    "x = prog.NewContinuousVariables(2)\n",
    "prog.AddCost(x[0]**2 + x[1] ** 2)\n",
    "prog.AddConstraint(x[0] + x[1] == 1)\n",
    "\n",
    "# Set the maximum iteration for IPOPT to be 1.\n",
    "# max_iter is a parameter of IPOPT solver, explained in\n",
    "# https://www.coin-or.org/Ipopt/documentation/node42.html\n",
    "prog.SetSolverOption(IpoptSolver().solver_id(), \"max_iter\", 1)\n",
    "solver = IpoptSolver()\n",
    "result = solver.Solve(prog, np.array([10, 1]), None)\n",
    "# With fewer maximum iteration, IPOPT hasn't converged to optimality yet (The true optimal is [0.5, 0.5])\n",
    "print(\"Success? \", result.is_success())\n",
    "print(result.get_solution_result())\n",
    "print(\"IPOPT x*= \", result.GetSolution(x))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Also note that setting the parameter of a solver **doesn't** mean that `result = Solve(prog)` will invoke that solver. The invoked solver is determined by Drake, to choose whichever solver it thinks most appropriate.\n",
    "\n",
    "In the following snippet, although we set the solver options for IPOPT, Drake chooses another solver (which can solve this particular problem in the closed form.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prog.SetSolverOption(IpoptSolver().solver_id(), \"max_iter\", 1)\n",
    "result = Solve(prog)\n",
    "print(result.get_solver_id().name())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Passing a SolverOptions to Solve function\n",
    "Another way of setting the solver options is to pass in a `SolverOptions` object as an argument to `Solve` function. `MathematicalProgram` will *not* store this `SolverOptions` object.\n",
    "\n",
    "In the following example, in the first `Solve` call, it uses the `SolverOptions` object to set the parameter for IPOPT; in the second `Solve` call, it uses the default IPOPT parameters, hence we get different results from two `Solve` calls."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prog = MathematicalProgram()\n",
    "x = prog.NewContinuousVariables(2)\n",
    "prog.AddCost(x[0]**2 + x[1] ** 2)\n",
    "prog.AddConstraint(x[0] + x[1] == 1)\n",
    "\n",
    "solver_options = SolverOptions()\n",
    "solver_options.SetOption(IpoptSolver().solver_id(), \"max_iter\", 1)\n",
    "solver = IpoptSolver()\n",
    "\n",
    "# Call Solve with solver_options, IPOPT will use `max_iter` = 1\n",
    "result = solver.Solve(prog, np.array([10, 1]), solver_options)\n",
    "print(\"Success? \", result.is_success())\n",
    "print(result.get_solution_result())\n",
    "# Call Solve without solver_options, IPOPT will use the default options.\n",
    "result = solver.Solve(prog, np.array([10, 1]), None)\n",
    "print(\"Success? \", result.is_success())\n",
    "print(result.get_solution_result())"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
